package com.icehelper.hades.base.util.id;


import java.math.BigInteger;

/**
 * 雪花算法ID生成
 * <p>一个long型的ID，通过bit位的不同段表示不同的含义，该long具有可读意义
 * <p>将long的64bit进行分段
 * <p>1. 1 bit 0填充，确保生成的都是正数
 * <p>2. 41 bit 毫秒数填充 2^41可表示的最大值
 * <p>3. 2 bit 机房ID 2^2 = 4 可表示机房数
 * <p>4. 4 bit 机器ID 2^4 = 16 每个机房可表示的机器数
 * <p>5. 10 bit 序号列 2^10 = 1024 每毫秒可以产生的最大值
 * <p>6. 6 bit 业务标示 2^6 = 64 可以标示不同业务
 *
 * @author Leon Sun
 * @since 2022/4/24
 */
public class SnowFlakeId {

  /**
   * 起始时间戳(2019-12-01)
   */
  private static final long START_TIMESTAMP = 1575129600000L;
  /**
   * 数据中心ID占用的位数
   */
  private static final long DATACENTER_ID_BITS = 2L;
  /**
   * 数据中心ID的最大值
   * <p>2的2次方，即这个值最大为4
   */
  private static final long MAX_DATACENTER_ID = -1L ^ (-1L << DATACENTER_ID_BITS);
  /**
   * 机器ID占用的位数
   */
  private static final long WORKER_ID_BITS = 4L;
  /**
   * 机器ID的最大值
   * <p>2的4次方，即这个值最大为16
   */
  public static final long MAX_WORKER_ID = -1L ^ (-1L << WORKER_ID_BITS);
  /**
   * 序号占用的位数
   */
  private static final long SEQUENCE_BITS = 10L;
  /**
   * 序号的最大值
   * <p>2的10次方,每个毫秒能产生1024个序号
   */
  private static final long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BITS);
  /**
   * 业务标示占用6位
   */
  private static final long BUSINESS_BITS = 6L;
  /**
   * 业务标示的最大值2的6次方，64
   */
  private static final long MAX_BUSINESS_ID = -1L ^ (-1L << BUSINESS_BITS);


  private static final long BUSINESS_ID_SHIFT = SEQUENCE_BITS;
  private static final long WORK_ID_SHIFT = SEQUENCE_BITS + BUSINESS_BITS;
  private static final long DATACENTER_ID_SHIFT = SEQUENCE_BITS + BUSINESS_BITS + WORKER_ID_BITS;
  private static final long TIMESTAMP_SHIFT =
      SEQUENCE_BITS + BUSINESS_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS;


  private long lastTimestamp = -1L;
  /**
   * 序号初始值
   */
  private long sequence = 0L;

  /**
   * 机器ID
   */
  private Long workerId;
  /**
   * 机房ID
   */
  private Long dataCenterId;

  public SnowFlakeId(Long workerId, Long dataCenterId) {
    if (workerId > MAX_WORKER_ID || workerId < 0) {
      throw new IllegalArgumentException("workerId Incorrect.");
    }

    if (dataCenterId > MAX_DATACENTER_ID || dataCenterId < 0) {
      throw new IllegalArgumentException("dataCenterId Incorrect.");
    }

    this.workerId = workerId;
    this.dataCenterId = dataCenterId;
  }

  public long nextId() {
    return nextId(0L);
  }

  public synchronized long nextId(long businessId) {
    if (businessId > MAX_BUSINESS_ID || businessId < 0) {
      throw new IllegalArgumentException("businessId Incorrect.");
    }

    long timestamp = timeGen();
    if (timestamp < lastTimestamp) {
      throw new RuntimeException();
    }

    if (timestamp == lastTimestamp) {
      sequence = (sequence + 1) & MAX_SEQUENCE;
      //毫秒内序列溢出
      if (sequence == 0) {
        //阻塞到下一个毫秒,获得新的时间戳
        timestamp = tilNextMillis(lastTimestamp);
      }
    } else {
      sequence = 0L;
    }

    lastTimestamp = timestamp;

    return ((timestamp - START_TIMESTAMP) << TIMESTAMP_SHIFT)
        | (dataCenterId << DATACENTER_ID_SHIFT)
        | (workerId << WORK_ID_SHIFT)
        | (businessId << BUSINESS_ID_SHIFT)
        | sequence;
  }

  private long tilNextMillis(long lastTimestamp) {
    long timestamp = timeGen();
    while (timestamp <= lastTimestamp) {
      timestamp = timeGen();
    }
    return timestamp;
  }

  /**
   * 当前时间精确到毫秒
   */
  private long timeGen() {
    return System.currentTimeMillis();
  }

  public static String parseId(Long id){
    String res = Long.toBinaryString(id);
    StringBuffer stringBuffer = new StringBuffer();

    int diff = 64 - res.length();
    while (diff > 0){
      stringBuffer.append("0");
      diff--;
    }

    stringBuffer.append(res);

    StringBuffer result = new StringBuffer();
    BigInteger bigInteger = new BigInteger(stringBuffer.substring(1,42),2);
    result.append("time of birth: ").append(bigInteger.longValue() + START_TIMESTAMP).append(",");
    result.append("dataCenterId: ").append(stringBuffer.substring(42,44)).append(",");
    result.append("workId:").append(stringBuffer.substring(44,48)).append(",");
    result.append("businessId:").append(stringBuffer.substring(48,54)).append(",");
    result.append("sequence:").append(stringBuffer.substring(54,64));

    return result.toString();
  }

}
